// SPDX-License-Identifier: GPL-2.0-only
// Copyright (c) 2022 Intel Corporation.

/*
 * Split lock test app to check if the locked instruction will trigger #AC
 */

#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <stdlib.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <stdio.h>

void catch_sigbus(int sig)
{
	printf("Caught SIGBUS/#AC due to split locked access\n");

	exit(-1);
}

/*
 * Atomically add 1 to *iptr using "lock addl" instruction.
 * Since *iptr crosses two cache lines, #AC is generated by the split lock.
 */
void do_split_locked_inst(int *iptr)
{
	/*
	 * The distance between iptr and next cache line is 3 bytes.
	 * Operand size in "addl" is 4 bytes. So iptr will span two cache
	 * lines. "lock addl" instruction will trigger #AC in hardware
	 * and kernel either delivers SIGBUS to this process or re-execute
	 * the instruction depending on
	 * /sys/kernel/debug/x86/split_lock/user_mode setting.
	 */
	asm volatile ("lock addl $1, %0\n\t"
		      : "=m" (*iptr));
}

/*
 * Test SIGBUS delivered after a lock instruction generates #AC for split lock
 * operand *iptr.
 */
void test_sigbus(int *iptr)
{
	pid_t pid;

	pid = fork();
	if (pid)
		return;

	/*
	 * The locked instruction will trigger #AC and kernel will deliver
	 * SIGBUS to this process. The SIGBUS handler in this process will
	 * verify that the signal is delivered and the process is killed then.
	 */
	do_split_locked_inst(iptr);
}

int main(int argc, char **argv)
{
	int *iptr;
	char *cptr;
	char *line = NULL;
	int ret = 0;
	size_t len;
	FILE *fp;

	fp = fopen("/proc/cpuinfo", "r");
	if (!fp)
		return 1;

	while (getline(&line, &len, fp) != -1) {
		if (!strncmp(line, "flags", 5)) {
			if (strstr(line, "split_lock_detect"))
				ret = 1;
			break;
		}
	}

	free(line);
	fclose(fp);
	if (!ret) {
		printf("split_lock_dect: [FAIL]\n");
		return !ret;
	}

	signal(SIGBUS, catch_sigbus);

	/*
	 * Enable Alignment Checking on x86_64.
	 * This will generate alignment check on not only split lock but also
	 * on any misalignment.
	 * Turn on this for reference only.
	 */
	/* __asm__("pushf\norl $0x40000,(%rsp)\npopf"); */

	/* aligned_alloc() provides 64-byte aligned memory */
	cptr = (char *)aligned_alloc(64, 128);

	/*
	 * Increment the pointer by 61, making it 3 bytes away from the next
	 * cache line and 4-byte *iptr across two cache line.
	 */
	iptr = (int *)(cptr + 61);

	test_sigbus(iptr);

	free(cptr);

	printf("Split Lock test - exits normally\n");

	return 0;
}
